﻿/*******************************************************************
Copyright (c) 2011 이재범

*  Permission is hereby granted, free of charge, to any person
*  obtaining a copy of this software and associated documentation
*  files (the "Software"), to deal in the Software without
*  restriction, including without limitation the rights to use,
*  copy, modify, merge, publish, distribute, sublicense, and/or sell
*  copies of the Software, and to permit persons to whom the
*  Software is furnished to do so, subject to the following
*  conditions:
* 
*  The above copyright notice and this permission notice shall be
*  included in all copies or substantial portions of the Software.
* 
*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
*  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
*  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
*  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
*  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
*  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
*  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
*  OTHER DEALINGS IN THE SOFTWARE.
*******************************************************************/

#include "equation.h"
#include "common.h"

void Equation::normalize_equation()
{
	//enclose_numbers();
	insert_missing_times();

	if(!is_normalized)
		return;

	Dictionary<string> plus; plus.push_back("plus");
	Dictionary<string> comma; comma.push_back("comma");

	// 교환 가능한 것들
	Dictionary<string>* chang_oper_table[4] = {&equal_symbol_list, &comma, &plus, &changeable_operator_list};

	// 수식의 전체를 중괄호로 감싼다. 
	equation.insert(0, "{");
	equation.insert(equation.length(), "}");

	for(int x = 0; x < 2; x ++)
	{
		insert_times_btw_consecutive_brakets();
		for(int i = 0; i < 4; i ++)
		{
			string::size_type location = 0;
			string operator_name;
			int find = 0;

			for(location = 0; location < equation.length(); location ++)
			{
				if(equation[location] == '\\')
				{
					if(is_operator(location, operator_name))
					{
						// 현재 처리하는데 맞는 연산자를 찾았다면
						if(chang_oper_table[i]->find(operator_name) != -1)
						{
							// 지금 찾은 연산자의 이름을 기억한다.
							string original_operator_name = operator_name;
							find = location + original_operator_name.length() + 1;
							equation.insert(find, "{");

							int count = 0;

							find ++;
							while (find < static_cast<int>(equation.length()))
							{
								if(equation[find] == '{' || equation[find] == '(')
									count ++;
								else if(equation[find] == '}' || equation[find] == ')')
									count --;

								// 어차피 수식 전체가 중괄호로 쌓여 있으므로 끝에 도달하면 자동으로
								// -1 이 된다. 
								if(count == -1)
								{
									equation.insert(find, "}");
									break;
								}
								// 이제 같은 종류의 연산자를 만났을 때를 처리해주어야 한다.
								else if(count == 0 && equation[find] == '\\')
								{
									if(is_operator(find, operator_name))
									{
										if(chang_oper_table[i]->find(operator_name) != -1)
										{
											equation.insert(find, "}");
											break;
										}
									}
								}

								find ++;
							}

							// 이번에는 왼쪽으로 가는 것으로 처리해야 한다.
							count = 0;
							equation.insert(location, "}");
							find = location - 1;

							while (find >= 0)
							{
								if(equation[find] == '{' || equation[find] == '(')
									count ++;
								else if(equation[find] == '}' || equation[find] == ')')
									count --;

								// 어차피 수식 전체가 중괄호로 쌓여 있으므로 끝에 도달하면 자동으로
								// 1 이 된다. 
								if(count == 1)
								{
									equation.insert(find + 1, "{");
									break;
								}
								// 이제 같은 종류의 연산자를 만났을 때를 처리해주어야 한다.
								else if(count == 0 && equation[find] == '\\')
								{
									if(is_operator(find, operator_name))
									{
										if(chang_oper_table[i]->find(operator_name) != -1)
										{
											equation.insert(find + operator_name.length() + 1, "{");
											break;
										}
									}
								}

								find --;
							}
							location = location + original_operator_name.length() + 2;

						}
						else
							location = location + operator_name.length();
					}
				}
			}
		}

		// 교환 가능하지 않는 연산자와 단항 연산자들은 따로 처리해야 한다. 
		string::size_type location = 0;
		string operator_name;
		for(location = 0; location < equation.length(); location ++)
		{
			if(equation[location] == '\\')
			{
				if(is_operator(location, operator_name))
				{
					// 교환 불가능한 연산자라면
					if(unchangeable_operator_list.find(operator_name) != -1)
					{
						int count = 0;
						int find = location + operator_name.length() + 1;
						equation.insert(find, "{");

						find ++;
						while (find < static_cast<int>(equation.length()))
						{
							if(equation[find] == '{' || equation[find] == '(')
								count ++;
							else if(equation[find] == '}' || equation[find] == ')')
								count --;
							if(count == -1)
							{
								equation.insert(find, "}");
								break;
							}
							find ++;
						}

						count = 0;
						equation.insert(location, "}");
						find = location - 1;

						while (find >= 0)
						{
							if(equation[find] == '{' || equation[find] == '(')
								count ++;
							else if(equation[find] == '}' || equation[find] == ')')
								count --;
							if(count == 1)
							{
								equation.insert(find + 1, "{");
								break;
							}
							find --;
						}
						location = location + operator_name.length() + 2;
					}
					
				}
				else
					location = location + operator_name.length();
			}
		}

		for(location = 0; location < equation.length(); location ++)
		{
			if(equation[location] == '\\')
			{
				if(is_operator(location, operator_name))
				{
					if(!is_two_parameter_operator(operator_name))
					{
						int count = 0;
						int find = location + operator_name.length() + 1;
						equation.insert(find, "{");

						find ++;
						while (find < static_cast<int>(equation.length()))
						{
							if(equation[find] == '{' || equation[find] == '(')
								count ++;
							else if(equation[find] == '}' || equation[find] == ')')
								count --;
							if(count == -1)
							{
								equation.insert(find, "}");
								break;
							}
							find ++;
						}
					}
				}
			}
		}
	}

	optimize_parenth();
	optimize_space();

	// 여기서 {}\plus 형태를 없앤다. 
	string::size_type location = equation.find("{}\\plus");
	while (location != string::npos)
	{
		equation.replace(location, 7, "");
		location = equation.find("{}\\plus");
	}

	// 연산자 표준 형태에 맞지 않는 경우
	location = equation.find("{}");
	if(location != string::npos)
		is_normalized = false; 
}
void Equation::insert_times_btw_consecutive_brakets()
{
	// {}{} 와 같이 중괄호가 연속되어서 나타나는 경우 그 사이에
	// \times 를 끼워 넣어 준다. 예를 들어서 
	// {a+b}{c+d} 와 같은 경우 {a+b} \times {c+d} 를 만든다.

	string::size_type location;
	for(location = 0; location < equation.length(); location ++)
	{
		if(equation[location] == '}' || equation[location] == ')')
		{
			string::size_type find = location;
			find ++;

			while (find <equation.length())
			{
				if(equation[find] == ' ')
				{
					find ++;
					continue;
				}
				else if(equation[find] == '{' || equation[find] == '(')
				{
					equation.insert(find , " \\times ");
					break;
				}
				else
					break;
			}
		}
	}
}
void Equation::insert_missing_times()
{
	string::size_type location = 0;

	string symbol;
	string operator_name;

	for(location = 0; location < equation.length(); location ++)
	{
		if(equation[location] == '\\')
		{
			get_symbol_name(location, symbol);
			location += symbol.length();
			continue;
		}
		if(is_alpha(equation[location]))
		{
			int find = location + 1;
			while (find < static_cast<int>(equation.length()))
			{
				if(equation[find] == ' ')
				{
					find ++;
					continue;
				}
				else if(equation[find] == '}' || equation[find] == ')')
					break;
				else if(equation[find] == '{' || equation[find] == '(')
				{
					equation.insert(find, " \\times "); // * 를 삽입
					break;
				}

				else if(equation[find] == '\\')
				{
					if(is_operator(find, operator_name))
					{
						// 뒤에 있는 연산자가 단항 연산자일 경우
						if(!is_two_parameter_operator(operator_name))
						{
							equation.insert(find, " \\times ");
						}
					}
					break;
				}
				else if(is_alpha(equation[find]))
				{
					equation.insert(find, " \\times ");
					break;
				}

				find ++;
			}

			find = location - 1;
			while (find  >= 0)
			{
				if(equation[find] == ' ')
				{
					find --;
					continue;
				}

				// 앞에 문자가 나타났다면 이미 다 처리된 것들
				// 왜냐하면 \plus a 인 경우 처리할 필요가 없고
				// \sin a 여도 처리할 필요가 없고
				// ba 였으면 이미 b 에 의해 b \times a 가 되었을 것이다. 
				else if(is_alpha(equation[find])) 
					break;

				else if(equation[find] == '{' || equation[find] == '(') // {a
					break;

				else if(equation[find] == '}' || equation[find] == ')') // }a -> } \times a
				{
					equation.insert(find + 1, " \\times ");
					location = location + 8;
					break;
				}
				else if(equation[find] == '\\')
					break;
				else
				{
					is_normalized = false;
					break;
				}
			}
		}
	}

	// 이제 단항 연산자 앞에 생략된 곱셈들, 예컨대
	// } \sin 과 같은 경우 } \times \sin 으로 변환해야 한다.

	for(location = 0; location < equation.length(); location ++)
	{
		if(equation[location] == '\\')
		{
			if(is_operator(location, operator_name))
			{
				if(!is_two_parameter_operator(operator_name))
				{
					// 나머지 경우들의 경우,
					// 예컨대 a \\ln 과 같은 형태나, \alpha \\ln 과 같은 경우, 혹은
					// \\cos \\cos 와 같은 형태의 경우 모두 이미 처리되었다. 
					// 따라서 오직 } \\ln 과 같은 형태만 주목하면 된다.
					int find = location - 1;
					while (find >= 0)
					{
						if(equation[find] == ' ')
						{
							find --;
							continue;
						}
						else if(equation[find] == '}' || equation[find] == ')')
						{
							equation.insert(location, " \\times ");
							break;
						}
						else
							break;
					}
				}
			}
		}
	}
}
void Equation::enclose_numbers()
{
	string::size_type location = 0;
	string operator_name;	

	string number;
	for(location = 0; location < equation.length(); location++)
	{
		if(equation[location] == '\\')
		{
			get_symbol_name(location, operator_name);
			location = location + operator_name.length();
			continue;
		}
		if(is_digit(equation[location]))
		{
			equation.insert(location, "{");

			location ++; // 다시 숫자를 가리키게 함.
			for (string::size_type i = location; i < equation.length(); i++)
			{
				if(is_digit(equation[i]) || equation[i] == '.') // 소수점!c
				{
					number.push_back(equation[i]);
				}
				else
					break;
			}

			location = location + number.length();
			number.clear();

			equation.insert(location, "}");
		}
	}
}
